package edu.ucla.whi.wanda;

import java.io.IOException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.ext.wadl.DocumentationInfo;
import org.restlet.ext.wadl.MethodInfo;
import org.restlet.ext.wadl.ParameterInfo;
import org.restlet.ext.wadl.ParameterStyle;
import org.restlet.ext.wadl.RepresentationInfo;
import org.restlet.ext.wadl.RequestInfo;
import org.restlet.ext.wadl.WadlServerResource;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.simpledb.AmazonSimpleDBAsync;
import com.amazonaws.services.simpledb.AmazonSimpleDBAsyncClient;
import com.amazonaws.services.simpledb.model.Attribute;
import com.amazonaws.services.simpledb.model.ReplaceableAttribute;
import com.amazonaws.services.simpledb.model.SelectRequest;
import com.amazonaws.util.json.JSONException;
import com.amazonaws.util.json.JSONObject;

public class BaseResource extends WadlServerResource {
	private static AmazonSimpleDBAsync sdb;

	protected HashMap<String, String> params = new HashMap<String, String>();
	
	// SimpleDB Domain names
	public static final String SUBJECTS_DOMAIN = "subjects";
	public static final String SENSORS_DOMAIN = "sensors";
	public static final String MEASUREMENTS_DOMAIN = "measurements";
	public static final String EVENTS_DOMAIN = "events";

	private String outputParamName;
	private MediaType outputMediaType = MediaType.APPLICATION_JSON;
	
	public BaseResource() {
		super();

		setAutoDescribing(true);
	}

	private void describeInput(MethodInfo info, List<ParameterInfo> params) {
		RequestInfo request = new RequestInfo();
		request.setParameters(params);
		info.setRequest(request);
	}
	
	private void describeOutput(MethodInfo info, List<ParameterInfo> params, String name) {
		RepresentationInfo repr = new RepresentationInfo();
		repr.setMediaType(outputMediaType);
		repr.setParameters(params);
		DocumentationInfo doc = new DocumentationInfo();
		doc.setTitle(name);
		repr.setDocumentation(doc);
		
		List<RepresentationInfo> representations = new LinkedList<RepresentationInfo>();
		representations.add(repr);
		info.getResponses().get(0).setRepresentations(representations);
	}
	
	@Override
	protected void describeGet(MethodInfo info) {
		super.describeGet(info);

		describeInput(info, getParams());
		describeOutput(info, outputParams(), outputParamName);
	}

	@Override
	protected void describePost(MethodInfo info) {
		super.describePost(info);
		
		describeInput(info, postParams());
		describeOutput(info, outputParams(), outputParamName);
	}
	
	@Override
	protected void describeDelete(MethodInfo info) {
		super.describeDelete(info);
		
		describeInput(info, deleteParams());
		describeOutput(info, outputParams(), outputParamName);
	}
	
	@Override
	public Representation handle() {
		// handle "method" parameter for browser client
		parseParams();
		
		// override method via param
		if (params.containsKey("method")) {
			getRequest().setMethod(new Method(params.get("method")));
		}

		String method = getRequest().getMethod().getName();
		
		List<ParameterInfo> params = null;
		if (method.equals("get")) {
			params = getParams();
		}
		else if (method.equals("post")) {
			params = postParams();
		}
		else if (method.equals("delete")) {
			params = deleteParams();
		}

		if (params != null) {
			checkParams(params);
		}
		
		return super.handle();
	}

	@Override
	protected Representation doHandle(Variant var) {
		parseParams();
		return super.doHandle(var);
	}
	
	protected List<ParameterInfo> getParams() {
		return null;
	}

	protected List<ParameterInfo> postParams() {
		return null;
	}

	protected List<ParameterInfo> deleteParams() {
		return null;
	}
	
	protected List<ParameterInfo> outputParams() {
		return null;
	}

	protected void initDescribe(String name, String description) {
		initDescribe(name, description, null);
	}

	protected void initDescribe(String name, String description, String outputParamName) {
		initDescribe(name, description, outputParamName, MediaType.APPLICATION_JSON);
	}
	
	protected void initDescribe(String name, String description, String outputParamName, MediaType outputMediaTyep) {
		setName(name);
		setDescription(description);
		this.outputParamName = outputParamName;
		this.outputMediaType = outputMediaTyep;
	}
	
	private void parseParams() {
		// POST params
		try {
			Form form = new Form(getRequestEntity().getText());
			for (Parameter param: form) {
				this.params.put(param.getName(), param.getValue());
			}
		}
		catch (IOException e) {
		}
		
		// GET params
		for (Parameter param: getRequest().getResourceRef().getQueryAsForm()) {
			this.params.put(param.getName(), param.getValue());
		}
	}
	
	private void checkParams(List<ParameterInfo> paramList) {
		// check required params
		for (ParameterInfo info: paramList) {
			if (info.isRequired() && 
				info.getStyle() == ParameterStyle.QUERY && 
				!params.containsKey(info.getName())) {
				throw new RuntimeException("Missing required parameter: " + info.getName());
			}
		}
	}
	
	protected static AWSCredentials getAWSCredentials() {
		try {
	        return new PropertiesCredentials(
	                BaseResource.class.getClassLoader().getResourceAsStream("AwsCredentials.properties"));
		}
		catch (IOException e) {
			throw new RuntimeException("AWS Credential file not found!");
		}
	}
	
	protected static AmazonSimpleDBAsync getDB() {
		// lazy instantiation
		if (sdb == null) {
			sdb = new AmazonSimpleDBAsyncClient(getAWSCredentials());
		}

		return sdb;
	}
	
	protected String newId() {
		return UUID.randomUUID().toString();
	}	
	
	protected static JSONObject makeJson(String id, List<Attribute> attributes)  {
		JSONObject json = new JSONObject();
		
		try {
			json.put("Id", id);
			
			for (Attribute attribute : attributes) {
				json.put(attribute.getName(), attribute.getValue());
			}
		}
		catch (JSONException e) {
			throw new RuntimeException(e.toString());
		}

		return json;
	}

	protected static SelectRequest buildSelectRequest(String from, String where, String order, String next, boolean idOnly) {
		StringBuilder sb = new StringBuilder();
		Formatter formatter = new Formatter(sb);

		// base syntax
		String source = idOnly ? "itemName()" : "*";
		formatter.format("select %s from `%s`", source, from);

		// Where condition
		if (where != null) {
			sb.append(" where ");
			sb.append(where);
		}
		
		// order by
		if (order != null) {
			sb.append(" order by ");
			sb.append(order);
		}

		// construct request
		SelectRequest request = new SelectRequest(sb.toString());
		if (next != null) {
			request.setNextToken(next);
		}

		return request;
	}

	protected List<ReplaceableAttribute> buildAttributesFromParams(List<ParameterInfo> paramList) {
		List<ReplaceableAttribute> attributes = new LinkedList<ReplaceableAttribute>();
		
		for (ParameterInfo info : paramList) {
			String val = params.get(info.getName());
			if (val != null) {
				attributes.add(new ReplaceableAttribute(info.getName(), val, true));
			}
		}
		
		return attributes;
	}
	
	protected ParameterInfo newArrayParameterInfo(String name, boolean required, String type, ParameterStyle style, String description) {
		ParameterInfo param = new ParameterInfo(name, required, type, style, description); 
		param.setRepeating(true);
		return param;
	}
}